/*
 * frtlock.c
 *
 * FreeRunner Transparent Lock
 *
 * Copyright (C)2010 Joachim Ott <Joachim.Ott@gmail.com>
 * http://code.google.com/p/frtlock/
 *
 * based on xtrlock.c, (C)1993,1994 Ian Jackson
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <X11/X.h>
#include <X11/Xlib.h>

#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <getopt.h>
#include <syslog.h>
#include <time.h>

#include "locked.xbm"
#include "locked-mask.xbm"

static int	lock_key = 177;		/* AUX key */
static int	verbose = 0;
static char *	bg_color = "black";
static char *	fg_color = "red";

static Display *	display;
static Window	window;
static XSetWindowAttributes	attr;

int main (int argc, char *argv[]) {
	int i, j, keep_locked;
	XEvent ev;
	Time t_last = 0;
	Pixmap csr_bits, csr_mask;
	XColor csr_fg, csr_bg, dummy;

	opterr = 0;	/* no messages from getopt() */

	while ((i = getopt (argc, argv, "k:c:v")) != -1) {
		switch (i) {
			case 'k' :
				lock_key = (int) optarg;
				break;
			case 'c' :
				fg_color = optarg;
				break;
			case 'v' :
				verbose++;
				break;
			default :
				fprintf (stderr, "usage: %s [-k keycode] [-c fg-color] [-v]\n", basename (argv[0]));
				return 1;
		}
	}

	/* all messages go to syslog from now on */
	openlog ("frtlock", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON);

	if (verbose)
		syslog (LOG_INFO, "FreeRunner Transparent Lock started");

	/* connect to X-server */
	if ((display = XOpenDisplay (NULL)) == NULL) {
		syslog (LOG_ERR, "cannot open display");
		return 1;
	}

	attr.override_redirect = True;

	window = XCreateWindow (display, DefaultRootWindow (display), 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &attr);

	XSelectInput (display, window, KeyPressMask | KeyReleaseMask | PointerMotionMask);

	csr_bits = XCreateBitmapFromData (display, window, (const char *) locked_bits, locked_width, locked_height);
	csr_mask = XCreateBitmapFromData (display, window, (const char *) locked_mask_bits, locked_mask_width, locked_mask_height);

	XAllocNamedColor (display, DefaultColormap (display, DefaultScreen (display)), bg_color, &dummy, &csr_bg);
	XAllocNamedColor (display, DefaultColormap (display, DefaultScreen (display)), fg_color, &dummy, &csr_fg);

	attr.cursor = XCreatePixmapCursor (display, csr_bits, csr_mask, &csr_fg, &csr_bg, locked_width / 2, locked_height / 2);

	XMapWindow (display, window);

	/* grab keyboard and pointer */
	for (j = 0; ; j++) {
		if (j >= 20) {
			syslog (LOG_ERR, "cannot grab keyboard after %d tries", j);
			return 1;
		}

		if ((i = XGrabKeyboard (display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime)) == GrabSuccess) {
			if (verbose)
				syslog (LOG_INFO, "grabbed keyboard after %d tries", j);
			break;
		}

		/* for some reason the grab doesn't work on the first try most of the time */
		if (i == AlreadyGrabbed) {
			struct timespec req, rem;

			req.tv_sec = 0;
			req.tv_nsec = 20000000;		/* = 20 msec */
			nanosleep (&req, &rem);
		}
		else {
			/* other reasons */
			syslog (LOG_ERR, "cannot grab keyboard: %d", i);
			return 1;
		}
	}

	if ((i = XGrabPointer (display, window, False, 0, GrabModeAsync, GrabModeAsync, None, attr.cursor, CurrentTime)) != GrabSuccess) {
		XUngrabKeyboard (display, CurrentTime);
		syslog (LOG_ERR, "cannot grab pointer: %d", i);
		return 1;
	}

	if (verbose)
		syslog (LOG_INFO, "FreeRunner Transparent Lock ready");

	/* wait for AUX key pressed twice in a second, then unlock screen */
	for (keep_locked = 1; keep_locked; ) {
		XNextEvent (display, &ev);

		if ((ev.type == KeyRelease) && (ev.xkey.keycode == lock_key)) {
			if (t_last == 0) {
				t_last = ev.xkey.time;
			}
			else {
				if ((ev.xkey.time - t_last) < 1000)
					keep_locked = 0;
				else
					t_last = ev.xkey.time;
			}
		}
	}

	if (verbose)
		syslog (LOG_INFO, "screen unlocked");

	return 0;
}
